import { PropType, computed, defineComponent, onMounted, onUnmounted, provide, reactive, ref, toRef, watch, watchEffect } from "vue";
import CarouselItem from "./Item";
import InnerItem from "./InnerItem";
import { useStyle } from "../use/useStyle";
import Space from "../Space";
import { FeatherChevronLeft } from "cui-vue-icons/feather";

export interface CarouselProps {
    height?: number,
    arrow?: boolean,
    autoPlay?: boolean,
    duration?: number,
    effect?: 'fade'|'slide'|'card',
    dotType?: 'dot'|'line'|'columnar',
    dotAlign?: 'left'|'right'|'top'|'bottom',
    dotColor?: string,
    dotActiveColor?: string,
    activeIndex?: number,
    itemsPerView?: number | 'auto',
    gutter?: number,
    draggable?: boolean,
    dir?: 'h'|'v',
    onChange?: (v: any) => void
}

type CarouselStore = {
    data: any[],
    activeIndex: number,
    unActiveIndex: number,
    activeKey: string,
    unActiveKey: string,
    nextKey: string,
    prevKey: string,
    startPos: number,
    currentPos: number,
    isSwiping: boolean,
    dir: 'normal'|'reverse'
}

export const CarouselContextKey = Symbol('CarouselContextKey');

const Carousel = defineComponent({
    name: 'Carousel',
    props: {
        height: {type: Number as PropType<CarouselProps['height']>},
        arrow: {type: Boolean as PropType<CarouselProps['arrow']>, default: false},
        autoPlay: {type: Boolean as PropType<CarouselProps['autoPlay']>, default: false},
        duration: {type: Number as PropType<CarouselProps['duration']>, default: 4000},
        effect: {type: String as PropType<CarouselProps['effect']>, default: 'slide'},
        dotType: {type: String as PropType<CarouselProps['dotType']>, default: 'dot'},
        dotAlign: {type: String as PropType<CarouselProps['dotAlign']>},
        dotColor: {type: String as PropType<CarouselProps['dotColor']>, default: 'rgba(var(--cui-grey-8), 0.3)'},
        dotActiveColor: {type: String as PropType<CarouselProps['dotActiveColor']>, default: 'rgba(var(--cui-white), 1)'},
        activeIndex: {type: Number as PropType<CarouselProps['activeIndex']>},
        itemsPerView: {type: [Number, String] as PropType<CarouselProps['itemsPerView']>, default: 1},
        gutter: {type: Number as PropType<CarouselProps['gutter']>, default: 0},
        draggable: {type: Boolean as PropType<CarouselProps['draggable']>},
        dir: {type: String as PropType<CarouselProps['dir']>, default: 'h'}
    },
    emits: ['change', 'update:activeIndex'],
    setup (props, { emit, slots }) {
        const activeIndex = ref(props.activeIndex ?? 0);
        const current = ref(activeIndex.value);
        const arrow = toRef(props, 'arrow');
        const dotType = toRef(props, 'dotType');
        const autoPlay = toRef(props, 'autoPlay');
        const duration = toRef(props, 'duration');
        const draggable = toRef(props, 'draggable');
        const effect = toRef(props, 'effect');
        const itemsPerView = toRef(props, 'itemsPerView');
        const dir = toRef(props, 'dir');
        const dotColor = toRef(props, 'dotColor');
        const dotActiveColor = toRef(props, 'dotActiveColor');
        const dotAlign = computed(() => props.dotAlign ?? (dir.value === 'h' ? 'bottom' : 'right'));
        const gutter = toRef(props, 'gutter');

        const classList = computed(() => ({
            'cm-carousel': true,
            [`cm-carousel-${effect.value}`]: effect.value,
            [`cm-carousel-${dir.value}`]: dir.value
        }));

        const wrap = ref();
        const list = ref();
        let playTimer: any = null;
        const arrowClasses = computed(() => ({
            'cm-carousel-arrow': true,
            [`cm-carousel-arrow-${arrow.value}`]: !!arrow.value,
        }));

        const arrowsClasses = computed(() => ({
            'cm-carousel-actions': true,
            'cm-carousel-actions-with-arrow': arrow.value,
            [`cm-carousel-actions-${dotAlign.value}`]: !!dotAlign.value,
        }));

        const dotClasses = computed(() => ({
            'cm-carousel-dots': true,
            [`cm-carousel-dots-${dotType.value}`]: !!dotType.value,
            [`cm-carousel-dots-${dotAlign.value}`]: !!dotAlign.value,
        }));
        let inited = false;
        const store = reactive({
            data: [],
            activeIndex: activeIndex.value,
            unActiveIndex: -1,
            activeKey: '',
            unActiveKey: '',
            nextKey: '',
            prevKey: '',
            startPos: 0,
            isSwiping: false,
            currentPos: 0,
            dir: 'normal'
        } as CarouselStore);

        const views = ref<number[]>(itemsPerView.value !== 'auto' ? new Array(Math.ceil(store.data.length / itemsPerView.value)).fill(1) : []);
        const loop = computed(() => (effect.value === 'slide' && itemsPerView.value === 1) || effect.value === 'card' || effect.value === 'fade');
        const listStyle = ref({});

        const getWidth = () => {
            return list.value ? (dir.value === 'h' ? list.value.getBoundingClientRect().width : list.value.getBoundingClientRect().height) : 0;
        };

        // 添加元素
        const addItem = (item: any) => {
            item.index = store.data.length;
            store.data.push(item);
            views.value = itemsPerView.value !== 'auto' ? new Array(Math.ceil(store.data.length / itemsPerView.value)).fill(1) : [];
        };

        // 获取子元素的总长度
        const getItemsWidth = () => {
            if (list.value) {
                const items: any[] = list.value.querySelectorAll('.cm-carousel-item');
                const allW = Array.from(items).reduce((a, b) => {
                    return a += dir.value === 'h' ? b.getBoundingClientRect().width : b.getBoundingClientRect().height;
                }, 0);
                return allW + gutter.value * (items.length - 1);
            }
            return 0;
        };

        const slideEffect = () => {
            const index = current.value;
            activeIndex.value;
            dir.value;

            if (index === views.value.length) {
                list.value.style.transitionDuration = '0ms';
                let offset = 0;
                if (store.isSwiping && store.activeIndex === views.value.length - 1) {
                    offset = store.currentPos - store.startPos;
                }
                list.value.style.transform = dir.value === 'h' ? `translateX(${offset}px)` : `translateY(${offset}px)`;
            }
            if (index === -1) {
                const w = getWidth() + gutter.value;
                list.value.style.transitionDuration = '0ms';
                let offset = 0;
                if (store.isSwiping && store.activeIndex === 0) {
                    offset = store.currentPos - store.startPos;
                }
                list.value.style.transform = dir.value === 'h' ? `translateX(${-(views.value.length + 1) * w + offset}px)` : `translateY(${-(views.value.length + 1) * w + offset}px)`;
            }
            transToActive();
        };

        const transToActive = () => {
            if (effect.value !== 'slide') {
                return;
            }
            setTimeout(() => {
                const index = activeIndex.value + (loop.value ? 1 : 0);
                const w = getWidth() + gutter.value;
                const duration = !inited ? 0 : 300;
                let offset = index * w;
                const allW = getItemsWidth();
                // 当前视窗超过了总长度
                if (offset + getWidth() > allW) {
                    offset = offset - getWidth() + allW % getWidth();
                }

                const transform = dir.value === 'h' ? `translateX(${-offset}px)` : `translateY(${-offset}px)`;
                listStyle.value = {
                    'gap': `${gutter.value}px`,
                    'transition-duration': `${duration}ms`,
                    'transform': transform
                };
                list.value.setAttribute('data-offset', -offset);
                inited = true;
            });
        };

        watch(() => [effect.value, current.value, activeIndex.value, dir.value, views.value], () => {
            if (!list.value) {
                return {};
            }

            if (effect.value === 'slide') {
                slideEffect();
            }
        });

        // 播放
        const play = () => {
            clearTimeout(playTimer);
            onNext();
            playTimer = setTimeout(() => {
                play();
            }, duration.value);
        };

        watch(() => props.activeIndex, (val) => {
            activeIndex.value = val;
        });

        onMounted(() => {
            if (wrap.value) {
                setTimeout(() => {
                    if (itemsPerView.value === 'auto') {
                        const w = getWidth();
                        const allW = getItemsWidth();
                        const viewsVal = Math.ceil(allW / w);
                        if (w) {
                            views.value = new Array(viewsVal).fill(1);
                        }
                    } else {
                        views.value = new Array(Math.ceil(store.data.length / itemsPerView.value)).fill(1);
                    }
                });

                if (autoPlay.value) {
                    playTimer = setTimeout(() => {
                        play();
                    }, duration.value);
                }
            }
        });

        onUnmounted(() => {
            if (playTimer) {
                clearTimeout(playTimer);
            }
        });

        watchEffect(() => {
            store.unActiveIndex >= 0 && (effect.value === 'fade' || effect.value === 'card')
                ? store.unActiveKey = store.data[store.unActiveIndex].id
                : false;
        });

        watchEffect(() => {
            const index = activeIndex.value;
            store.activeIndex = index;
            if ((effect.value === 'fade' || effect.value === 'card') && store.data?.length) {
                store.prevKey = store.data[(views.value.length + index - 1) % views.value.length].id;
                store.nextKey = store.data[(index + 1) % views.value.length].id;
                store.activeKey = store.data[index].id;
            }
        });

        const onNext = () => {
            store.unActiveIndex = store.activeIndex;
            if (loop.value) {
                current.value = store.activeIndex + 1;
                activeIndex.value = (store.activeIndex + 1) % views.value.length;
            } else {
                const lastIndex = store.activeIndex;
                const cur = Math.min(lastIndex + 1, views.value.length - 1);
                current.value = cur;
                activeIndex.value = cur;
                if (cur === lastIndex && draggable.value) {
                    transToActive();
                }
            }
            emit('change', activeIndex.value);
            emit('update:activeIndex', activeIndex.value);
        };

        const onPrev = () => {
            store.unActiveIndex = store.activeIndex;
            if (loop.value) {
                current.value = store.activeIndex - 1;
                activeIndex.value = (views.value.length + store.activeIndex - 1) % views.value.length;
            } else {
                const lastIndex = store.activeIndex;
                const cur = Math.max(store.activeIndex - 1, 0);
                current.value = cur;
                activeIndex.value = cur;
                if (cur === lastIndex && draggable.value) {
                    transToActive();
                }
            }
            emit('change', activeIndex.value);
            emit('update:activeIndex', activeIndex.value);
        };

        const onDotClick = (num: number) => {
            store.unActiveIndex = store.activeIndex;
            current.value = num;
            activeIndex.value = num;
            emit('change', activeIndex.value);
            emit('update:activeIndex', activeIndex.value);
        };

        const onSwipe = (swipe: any) => {
            if (effect.value === 'slide') {
                listStyle.value = {
                    'gap': `${gutter.value}px`,
                    'transition-duration': `0ms`,
                    'transform': dir.value === 'h' ? `translateX(${store.startPos - swipe.distanceX.value}px)` : `translateY(${store.startPos - swipe.distanceY.value}px)`
                };
            }
            store.currentPos = store.startPos - (dir.value === 'h' ? swipe.distanceX.value : swipe.distanceY.value);
        };

        const onSwipeStart = () => {
            const offset = list.value.getAttribute('data-offset');
            store.isSwiping = true;
            store.startPos = parseFloat(offset);
        };

        const onSwipeEnd = (direction: string, duration: number) => {
            if (duration && duration > 500) {
                effect.value === 'slide' ? transToActive() : false;
                return;
            }

            if (dir.value === 'h') {
                if (direction === 'right') {
                    onPrev();
                } else if (direction === 'left') {
                    onNext();
                } else {
                    effect.value === 'slide' ? transToActive() : false;
                }
            }
            if (dir.value === 'v') {
                if (direction === 'down') {
                    onPrev();
                } else if (direction === 'up') {
                    onNext();
                } else {
                    effect.value === 'slide' ? transToActive() : false;
                }
            }
            store.isSwiping = false;
        };

        const style = () => useStyle({height: (props.height ?? 250) + 'px'});

        provide(CarouselContextKey, {store, effect, itemsPerView, onSwipe, onSwipeStart, onSwipeEnd, draggable, addItem});

        return () => <div class={classList.value} style={style()} ref={wrap}>
            {slots.default?.()}
            <div class={arrowsClasses.value}>
                <ul class={dotClasses.value} style={{
                    "--cui-carousel-dot-color": dotColor.value,
                    "--cui-carousel-active-dot-color": dotActiveColor.value,
                }}>
                    {
                        views.value.map((item: any, index: number) => {
                            const dotClass = () => ({'cm-carousel-dot': true, 'cm-carousel-dot-active': store.activeIndex === index});
                            return <li class={dotClass()} onClick={() => {
                                onDotClick(index);
                            }} />;
                        })
                    }
                </ul>
                {
                    arrow.value
                        ? <Space dir={dotAlign.value === 'bottom' || dotAlign.value === 'top' ? 'h' : 'v'}>
                            <div class={arrowClasses.value} x-placement="left" onClick={onPrev}>
                                {
                                    dir.value === 'h' ? <FeatherChevronLeft /> : <FeatherChevronLeft rotate={90}/>
                                }
                            </div>
                            <div class={arrowClasses.value} x-placement="right" onClick={onNext}>
                                {
                                    dir.value === 'h' ? <FeatherChevronLeft rotate={180}/> : <FeatherChevronLeft rotate={-90}/>
                                }
                            </div>
                        </Space>
                        : null
                }
            </div>
            <div class="cm-carousel-list" ref={list} style={listStyle.value}>
                {
                    loop.value && store.data.length
                        ? <InnerItem index={-1} data={store.data[store.data.length - 1]}/>
                        : null
                }
                {
                    store.data.map((item: any, index: number) => {
                        return <InnerItem key={item.id} index={index} data={item}/>;
                    })
                }
                {
                    loop.value && store.data.length
                        ? <InnerItem index={store.data.length} data={store.data[0]}/>
                        : null
                }
            </div>
        </div>;
    },
});

Carousel.Item = CarouselItem;

export default Carousel;
